package com.wolf.util.http;

import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.*;

/**
 * @author sdyang
 * @create 2017-12-16 15:28
 **/
public class HttpClientFactory {

    private static final String[] supportedProtocols = new String[]{"TLSv1"};

    private static int timeout = 30000;                //socket time out (default 30 s)

    private static int retryExecutionCount = 2;        //HttpClient retry execution count.

    /**
     * @return
     */
    public static CloseableHttpClient createHttpClient() {
        try {
            SSLContext sslContext = SSLContexts.custom().build();
            BasicHttpClientConnectionManager connMrg = new BasicHttpClientConnectionManager();
            SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(timeout).build();
            connMrg.setSocketConfig(socketConfig);
            SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
            return HttpClientBuilder.create()
                    .setConnectionManager(connMrg)
                    .setSSLSocketFactory(sf)
                    .setRetryHandler(new HttpRequestRetryHandlerImpl())
                    .build();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param maxTotal
     * @param maxPerRoute
     * @return
     */
    public static CloseableHttpClient createHttpClient(int maxTotal, int maxPerRoute) {
        try {
            SSLContext sslContext = SSLContexts.custom().build();
            SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
            poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(maxPerRoute);
            SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(timeout).build();
            poolingHttpClientConnectionManager.setDefaultSocketConfig(socketConfig);
            return HttpClientBuilder.create()
                    .setConnectionManager(poolingHttpClientConnectionManager)
                    .setSSLSocketFactory(sf)
                    .setRetryHandler(new HttpRequestRetryHandlerImpl())
                    .build();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param keystore
     * @param keyPassword
     * @return
     */
    public static CloseableHttpClient createKeyMaterialHttpClient(KeyStore keystore, String keyPassword) {
        return createKeyMaterialHttpClient(keystore, keyPassword, supportedProtocols);
    }

    /**
     * @param keystore
     * @param keyPassword
     * @param supportedProtocols
     * @return
     */
    public static CloseableHttpClient createKeyMaterialHttpClient(KeyStore keystore, String keyPassword, String[] supportedProtocols) {
        try {
            SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keystore, keyPassword.toCharArray()).build();
            SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sslContext, supportedProtocols,
                    null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
            SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(timeout).build();
            return HttpClientBuilder.create()
                    .setDefaultSocketConfig(socketConfig)
                    .setSSLSocketFactory(sf)
                    .setRetryHandler(new HttpRequestRetryHandlerImpl())
                    .build();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void setTimeout(int timeout) {
        HttpClientFactory.timeout = timeout;
    }

    public static void setRetryExecutionCount(int retryExecutionCount) {
        HttpClientFactory.retryExecutionCount = retryExecutionCount;
    }

    /**
     *
     */
    private static class HttpRequestRetryHandlerImpl implements HttpRequestRetryHandler {
        public boolean retryRequest(
                IOException exception,
                int executionCount,
                HttpContext context) {
            if (executionCount > retryExecutionCount) {
                return false;
            }
            if (exception instanceof InterruptedIOException) {
                return false;
            }
            if (exception instanceof UnknownHostException) {
                return false;
            }
            if (exception instanceof ConnectTimeoutException) {
                return true;
            }
            if (exception instanceof SSLException) {
                return false;
            }
            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
            if (idempotent) {
                // Retry if the request is considered idempotent
                return true;
            }
            return false;
        }

    }

}
